#include"semantic.h"
#include"def.h"
using namespace std;
semanticError* error2 = new semanticError();
extern string filename;
semanticError::semanticError(){
  
}


void semanticError::add(string error){
  for(string i:errors){
    if(i==error){
      return;
    }
  }
  errors.push_back(error);
}

void semanticError::display(){
  for(string error:errors){
    printf("%s:%s\n",filename.c_str(),error.c_str());
  }
}

semanticDriver::semanticDriver(){

}

semanticDriver::semanticDriver(driver* myDriver):driver(myDriver){
  switch(myDriver->getNode()->nodeType){
    case SYM_COMPUNIT    :break;
    case SYM_CONSTDECL   :driver::addop({DECL_check});       break;
    case SYM_VARDECL     :driver::addop({DECL_check});       break;
    case SYM_CONSTDEF    :break;
    case SYM_VARDEF      :break;
    case SYM_FUNCDEF     :driver::addop({DEF_check});        break;
    case SYM_CONSTINITVAL:driver::addop({INITVAL_check});    break;
    case SYM_INITVAL     :driver::addop({INITVAL_check});    break;
    case SYM_FUNCFPARAMS :break;
    case SYM_PARAM       :driver::addop({DEF_check});        break;
    case SYM_BLOCK       :break;
    case SYM_BRANCH      :break;
    case SYM_IF          :break;
    case SYM_ELSE        :break;
    case SYM_WHILE       :driver::addop({LOOP_push});
                          driver::addexit({LOOP_pop});       break;
    case SYM_FOR         :break;
    case SYM_BREAK       :driver::addop({BREAK_check});      break;
    case SYM_CONTINUE    :driver::addop({CONTINUE_check});   break;
    case SYM_RETURN      :driver::addop({RETURN_check});     break;
    case SYM_COND        :break;
    case SYM_FUNC        :driver::addop({FUNC_check});       break;
    case SYM_CONSTEXP    :break;
    case SYM_EXP         :break;
    case SYM_BRAKET      :driver::addop({OP1_check,BRAKET_check});     break;
    case SYM_ASSIGN      :driver::addop({OP2_check});        break;
    case SYM_ADD         :driver::addop({OP2_check});        break;
    case SYM_MINUS       :driver::addop({OP2_check});        break;
    case SYM_MUL         :driver::addop({OP2_check});        break;
    case SYM_DIV         :driver::addop({OP2_check});        break;
    case SYM_MOD         :driver::addop({OP2_check});        break;
    case SYM_NEGATIVE    :driver::addop({OP1_check});        break;
    case SYM_NOT         :driver::addop({OP1_check});        break;
    case SYM_AND         :driver::addop({OP2_check});        break;
    case SYM_OR          :driver::addop({OP2_check});        break;
    case SYM_GRATER      :driver::addop({OP2_check});        break;
    case SYM_LESS        :driver::addop({OP2_check});        break;
    case SYM_NOTLESS     :driver::addop({OP2_check});        break;
    case SYM_NOTGRATER   :driver::addop({OP2_check});        break;
    case SYM_EQUAL       :driver::addop({OP2_check});        break;
    case SYM_NOTEQUAL    :driver::addop({OP2_check});        break;
    case SYM_EMPTY       :break;
    case SYM_LIST        :break;
    case SYM_INT         :break;
    case SYM_FLOAT       :break;
    case SYM_TYPE_INT    :break;
    case SYM_TYPE_FLOAT  :break;
    case SYM_TYPE_VOID   :break;
    case SYM_ID          :driver::addop({ID_check});         break;
    case SYM_M           :break;
    case SYM_N           :break;
  }
}

semanticDriver* semanticDriver::getSemanticDriver(driver* myDriver){
  driver *iDriver = myDriver;
  semanticDriver *iSemanticDriver = NULL;
  while(iSemanticDriver == NULL){
    iSemanticDriver = dynamic_cast<semanticDriver*>(iDriver);
    iDriver = iDriver->myDriver;
    if(iDriver==NULL&&iSemanticDriver==NULL)return NULL;
  }
  return iSemanticDriver;
}

int BREAK_check(driver* originDriver){
  semanticDriver *myDriver = (semanticDriver*)originDriver;  
  node* n = myDriver->getNode();
  if(error2->loops.empty())
    error2->add(to_string(n->lineno)+" break statement not within a loop");
  else{
    myDriver->block = error2->loops.back();
  }
  return 1;
}

int CONTINUE_check(driver* originDriver){
  semanticDriver *myDriver = (semanticDriver*)originDriver;  
  node* n = myDriver->getNode();
  if(error2->loops.empty())
    error2->add(to_string(n->lineno)+" continue statement not within a loop");
  else{
    myDriver->block = error2->loops.back();
  }
  return 1;
}

int sym_check(sym* id,int lineno){
  int found = mytable->find(id,1);
  if(found==1){
      string error1 = to_string(lineno)+" redefinition of ‘"+id->name+"’";
      error2->add(error1);
      return -1;
  }else if(found==2){
      string error1 = to_string(lineno)+" ‘"+id->name+"’ redeclared as different kind of symbol";
      error2->add(error1);
      return -1;
  }
  return 1;
}

int DEF_check(driver* originDriver){
  semanticDriver *myDriver = (semanticDriver*)originDriver;  
  node* n = myDriver->getNode();
  sym* id = symDriver::getSymDriver(n->myDriver)->id;
  if(sym_check(id,n->lineno)<0)return -1;
  return 1;
}

int DECL_check(driver* originDriver){
  semanticDriver *myDriver = (semanticDriver*)originDriver;  
  node* n = myDriver->getNode();
  vector<sym*> ids = symDriver::getSymDriver(n->myDriver)->ids;
  int res = 1;
  for(int i=1;i<n->children.size();i++){
    node *n1 = n->children[i];
    sym * id = ids[i-1];
    if(sym_check(id,n->lineno)<0)res = -1;
  }
  return res;
  return 1;
}

int ID_check(driver* originDriver){
  semanticDriver *myDriver = (semanticDriver*)originDriver;  
  node* n = myDriver->getNode();
  if(n->parent->parent->nodeType==SYM_FUNCDEF
   ||n->parent->nodeType==SYM_CONSTDEF
   ||n->parent->nodeType==SYM_VARDEF
   ||n->parent->nodeType==SYM_PARAM){
    return 1;
  }
  sym *id = mytable->find(*(n->type_val));
  if(id==NULL){
    string error1 = to_string(n->lineno)+" ‘"+*(n->type_val)+"’ undeclared";
    error2->add(error1);
    return -1;
  }else{
    
  }
  return 1;
}

int RETURN_check(driver* originDriver){
  semanticDriver *myDriver = (semanticDriver*)originDriver;  
  node* n = myDriver->getNode();
  node* i = n;
  while(i!=root&&i->nodeType!=SYM_FUNCDEF){
    i = i->parent;
  }
  if(n->children.empty()){
    if(i->children[0]->children[0]->nodeType!=SYM_TYPE_VOID){
      string error1 = to_string(n->lineno)+" ‘return’ without a value, in function returning non-void";
      error2->add(error1);
      return -1;
    }
  }else{
    if(i->children[0]->children[0]->nodeType==SYM_TYPE_VOID){
      string error1 = to_string(n->lineno)+" ‘return’ with a value, in function returning void";
      error2->add(error1);
      return -1;
    }
  }
  return 1;
}

int OP1_check(driver* originDriver){
  semanticDriver *myDriver = (semanticDriver*)originDriver;  
  node* n = myDriver->getNode();
  node* op = n->children[0];
  sym* id = op->nodeType==SYM_ID?mytable->find(*(op->type_val)):NULL;
  if((id&&id->flag=='F')||
     (id&&id->dimension!=0&&op->dimension==0)   ||
     (id&&id->dimension==0&&op->dimension!=0)   ||
     (id&&id->dimension!=op->dimension)){
    string error1 = to_string(n->lineno)+" invalid operands to binary operator "+n->getName();
    error2->add(error1);
    return -1;
  }
  return 1;
}

int OP2_check(driver* originDriver){
  semanticDriver *myDriver = (semanticDriver*)originDriver;  
  node* n = myDriver->getNode();
  node* op1 = n->children[0];
  node* op2 = n->children[1];
  sym* id1 = op1->nodeType==SYM_ID?mytable->find(*(op1->type_val)):NULL;
  sym* id2 = op2->nodeType==SYM_ID?mytable->find(*(op2->type_val)):NULL;
  if(n->nodeType==SYM_ASSIGN){
    if(id1&&id1->flag=='F'){
      string error1 = to_string(n->lineno)+" lvalue required as left operand of assignment";
      error2->add(error1);
      return -1;
    }
    if(id1&&id1->dimension!=0&&op1->dimension==0){
      string error1 = to_string(n->lineno)+" assignment to expression with array type";
      error2->add(error1);
      return -1;
    }
    if(id1&&id1->dimension==0&&op1->dimension!=0){
      string error1 = to_string(n->lineno)+" subscripted value is not an array";
      error2->add(error1);
      return -1;
    }
    if(id2&&id2->flag=='F'){
      string error1 = to_string(n->lineno)+" function can't assigned to lvalue";
      error2->add(error1);
      return -1;
    }
    if(id2&&id2->dimension!=0&&op2->dimension==0){
      string error1 = to_string(n->lineno)+" subscripted value is not an array";
      error2->add(error1);
      return -1;
    }
    if(id2&&id2->dimension==0&&op2->dimension!=0){
      string error1 = to_string(n->lineno)+" subscripted value is an array";
      error2->add(error1);
      return -1;
    }
    if((id1&&id1->dimension!=op1->dimension)||
       (id2&&id2->dimension!=op2->dimension)){
      string error1 = to_string(n->lineno)+" error num of array subscript";
      error2->add(error1);
      return -1;
    }
    
  }else{
    if(((id1&&id1->flag=='F')||(id2&&id2->flag=='F'))||
       (id1&&id1->dimension!=0&&op1->dimension==0)   ||
       (id2&&id2->dimension!=0&&op2->dimension==0)   ||
       (id1&&id1->dimension==0&&op1->dimension!=0)   ||
       (id2&&id2->dimension==0&&op2->dimension!=0)   ||
       (id1&&id1->dimension!=op1->dimension)   ||
       (id2&&id2->dimension!=op2->dimension)         ){
      string error1 = to_string(n->lineno)+" invalid operands to binary operator "+n->getName();
      error2->add(error1);
      return -1;
    }
  }
  return 1;
}

int INITVAL_check(driver* originDriver){
  semanticDriver *myDriver = (semanticDriver*)originDriver;  
  node* n = myDriver->getNode();
  node* op = n->children[0];
  sym* id = op->nodeType==SYM_ID?mytable->find(*(op->type_val)):NULL;
  if(id&&id->flag=='F'){
    string error1 = to_string(n->lineno)+" function can't assigned to lvalue";
    error2->add(error1);
    return -1;
  }
  if(id&&id->dimension==0&&op->dimension!=0){
    string error1 = to_string(n->lineno)+" subscripted value is not an array";
    error2->add(error1);
    return -1;
  }
  if(id&&id->dimension!=0&&op->dimension==0){
    string error1 = to_string(n->lineno)+" subscripted value is an array";
    error2->add(error1);
    return -1;
  }
  if(id&&id->dimension!=op->dimension){
    string error1 = to_string(n->lineno)+" error num of array subscript";
    error2->add(error1);
    return -1;
  }
  return 1;
}

int FUNC_check(driver* originDriver){
  semanticDriver *myDriver = (semanticDriver*)originDriver;  
  node* n = myDriver->getNode();
  string funcName = *(n->type_val);
  sym *id = mytable->find(funcName);
  if(id==NULL)return 1;
  if(id->flag!='F'){    
    string error1 = to_string(n->lineno)+" ‘"+funcName+"’ is not a function";
    error2->add(error1);
  }else{
    if(n->children.size()>id->paramnum){
      string error1 = to_string(n->lineno)+" too many arguments to function ‘"+id->name+"’";
      error2->add(error1);
      return -1;
    }else if(n->children.size()<id->paramnum){
      string error1 = to_string(n->lineno)+" too few arguments to function ‘"+id->name+"’";
      error2->add(error1);
      return -1;
    }
  }
  return 1;
}

int float_check(node* n){
  if(n->nodeType==SYM_ID){
    sym *id = mytable->find(*(n->type_val));
    if(id->type=="float")return 1;
  }else if(n->nodeType==SYM_FLOAT){
    return 1;
  }
  for(node* i:n->children){
    if(float_check(i))return 1;
  }
  return 0;
}

int BRAKET_check(driver* originDriver){
  semanticDriver *myDriver = (semanticDriver*)originDriver;  
  node* n = myDriver->getNode();
  if(float_check(n)){
    string error1 = to_string(n->lineno)+" array subscript is not an integer";
    error2->add(error1);
    return -1;
  }
  return 1;
}

int getLevel(node* n){
  symDriver* myDriver = symDriver::getSymDriver(n->myDriver);
  return myDriver->level;
}

int LOOP_push(driver* originDriver){
  semanticDriver *myDriver = (semanticDriver*)originDriver;  
  node* n = myDriver->getNode();
  error2->loops.push_back(n);
  return 1;
}

int LOOP_pop(driver* originDriver){
  error2->loops.pop_back();
  return 1;
}

